#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest.h"
#include "pw_pbl/RefArray.hpp"

using pw_pbl::RefArray;

TEST_CASE("RefArray"){
	SUBCASE("iterator"){
		using type = RefArray<int, 7>;
		SUBCASE("non-const iterator"){
			using iterator = type::iterator;
			int a[] = {0x0123, 0x4567, 0x89AB, 0xCDEF, 0x1032, 0x5476, 0x98BA, 0xDCFE};
			int* b[] = {&a[2], &a[1], &a[0], &a[3], &a[7], &a[4], &a[5], &a[6]};
			iterator c = {&b[4]};
			CHECK(std::is_same_v<int, iterator::value_type>);
			CHECK(c == &b[4]);
			CHECK(*c == 0xDCFE);
			*c = 0xEFAC;
			CHECK(a[7] == 0xEFAC);
			CHECK(c[2] == 0x5476);
			CHECK(c[-3] == 0x4567);
			CHECK(++c == &b[5]);
			CHECK(c == &b[5]);
			CHECK(c++ == &b[5]);
			CHECK(c == &b[6]);
			CHECK(--c == &b[5]);
			CHECK(c == &b[5]);
			CHECK(c-- == &b[5]);
			CHECK(c == &b[4]);
			CHECK(c + 3 == &b[7]);
			CHECK(c - 4 == &b[0]);
			CHECK(3 + c == &b[7]);
			c += -2;
			CHECK(c == &b[2]);
			c -= -4;
			CHECK(c == &b[6]);
			CHECK(c - &b[2] == 4);
			CHECK(c != &b[1]);
			CHECK(c != &b[4]);
			CHECK(c != &b[7]);
			CHECK(c < &b[7]);
			CHECK_FALSE(c < &b[6]);
			CHECK_FALSE(c < &b[5]);
			CHECK(c > &b[5]);
			CHECK_FALSE(c > &b[6]);
			CHECK_FALSE(c > &b[7]);
			CHECK(c <= &b[7]);
			CHECK(c <= &b[6]);
			CHECK_FALSE(c <= &b[5]);
			CHECK(c >= &b[5]);
			CHECK(c >= &b[6]);
			CHECK_FALSE(c >= &b[7]);
		}
		SUBCASE("const iterator"){
			using iterator = type::const_iterator;
			int a[] = {0x0123, 0x4567, 0x89AB, 0xCDEF, 0x1032, 0x5476, 0x98BA, 0xDCFE};
			int*const b[] = {&a[2], &a[1], &a[0], &a[3], &a[7], &a[4], &a[5], &a[6]};
			iterator c = {&b[4]};
			CHECK(std::is_same_v<int, iterator::value_type>);
			CHECK(c == &b[4]);
			CHECK(*c == 0xDCFE);
			*c = 0xEFAC;
			CHECK(a[7] == 0xEFAC);
			CHECK(c[2] == 0x5476);
			CHECK(c[-3] == 0x4567);
			CHECK(++c == &b[5]);
			CHECK(c == &b[5]);
			CHECK(c++ == &b[5]);
			CHECK(c == &b[6]);
			CHECK(--c == &b[5]);
			CHECK(c == &b[5]);
			CHECK(c-- == &b[5]);
			CHECK(c == &b[4]);
			CHECK(c + 3 == &b[7]);
			CHECK(c - 4 == &b[0]);
			CHECK(3 + c == &b[7]);
			c += -2;
			CHECK(c == &b[2]);
			c -= -4;
			CHECK(c == &b[6]);
			CHECK(c - &b[2] == 4);
			CHECK(c != &b[1]);
			CHECK(c != &b[4]);
			CHECK(c != &b[7]);
			CHECK(c < &b[7]);
			CHECK_FALSE(c < &b[6]);
			CHECK_FALSE(c < &b[5]);
			CHECK(c > &b[5]);
			CHECK_FALSE(c > &b[6]);
			CHECK_FALSE(c > &b[7]);
			CHECK(c <= &b[7]);
			CHECK(c <= &b[6]);
			CHECK_FALSE(c <= &b[5]);
			CHECK(c >= &b[5]);
			CHECK(c >= &b[6]);
			CHECK_FALSE(c >= &b[7]);
		}
	}
	SUBCASE("non-const non-const"){
		std::array a = {0x0123, 0x4567, 0x89AB, 0xCDEF, 0x1032, 0x5476, 0x98BA, 0xDCFE};
		RefArray b = {a[3], a[0], a[2], a[1], a[7], a[4], a[6], a[5]};
		static_assert (b.size() == 8);
		static_assert (std::is_same_v<decltype(b)::value_type, int>);
		CHECK(b.at(3) == 0x4567);
		CHECK(b.at(6) == 0x98BA);
		CHECK_THROWS(b.at(8));
		CHECK_THROWS(b.at(9));
		CHECK_THROWS(b.at(std::size_t(-1)));
		b.at(6) = 0x78429832;
		CHECK(a[6] == 0x78429832);
		CHECK(b[0] == 0xCDEF);
		CHECK(b[5] == 0x1032);
		CHECK(b[6] == 0x78429832);
		b[6] = 0x3344BBAA;
		CHECK(a[6] == 0x3344BBAA);
		CHECK(b.front() == 0xCDEF);
		CHECK(b.back() == 0x5476);
		CHECK(reinterpret_cast<void*>(b.data()) == reinterpret_cast<void*>(&b));
		CHECK(std::size(b.data()) == b.size());
		CHECK(b.begin() == &b.data()[0]);
		CHECK(b.end() == &b.data()[b.size()]);
		CHECK(b.cbegin() == &b.data()[0]);
		CHECK(b.cend() == &b.data()[b.size()]);
		CHECK(b.rbegin().base() == &b.data()[b.size()]);
		CHECK(b.rend().base() == &b.data()[0]);
		CHECK(b.crbegin().base() == &b.data()[b.size()]);
		CHECK(b.crend().base() == &b.data()[0]);
		auto it = b.begin();
		CHECK(*it++ == 0xCDEF);
		CHECK(*it++ == 0x0123);
		CHECK(*it++ == 0x89AB);
		CHECK(*it++ == 0x4567);
		CHECK(*it++ == 0xDCFE);
		CHECK(*it++ == 0x1032);
		CHECK(*it++ == 0x3344BBAA);
		CHECK(*it++ == 0x5476);
		CHECK(it == b.end());
		auto rit = b.rbegin();
		CHECK(*rit++ == 0x5476);
		CHECK(*rit++ == 0x3344BBAA);
		CHECK(*rit++ == 0x1032);
		CHECK(*rit++ == 0xDCFE);
		CHECK(*rit++ == 0x4567);
		CHECK(*rit++ == 0x89AB);
		CHECK(*rit++ == 0x0123);
		CHECK(*rit++ == 0xCDEF);
		CHECK(rit == b.rend());
		CHECK_FALSE(b.empty());
		auto c = a;
		b.fill(0x5A55A5A5);
		for(auto& i : a)
			CHECK(i == 0x5A55A5A5);
		auto d = pw_pbl::make_RefArray(c[3], c[0], c[2], c[1], c[7], c[4], c[6], c[5]);
		b = d;
		CHECK(c == a);
		const auto e = a;
		b.fill(0x22446688);
		b.swap(d);
		for(auto& i : c)
			CHECK(i == 0x22446688);
		CHECK(e == a);
		b.fill(0x33557799);
		for(auto& i : a)
			CHECK(i == 0x33557799);
		const auto f = pw_pbl::make_RefArray(e[3], e[0], e[2], e[1], e[7], e[4], e[6], e[5]);
		b = f;
		CHECK(e == a);
		CHECK(b == f);
		CHECK_FALSE(b != f);
		a[5] = 0x88423;
		CHECK_FALSE(b == f);
		CHECK(b != f);
		auto& g = b.slice<2, 5>();
		CHECK(g.size() == 4);
		CHECK(g.begin() == b.begin() + 2);
		CHECK(g.end() == b.begin() + 6);
	}
	SUBCASE("const non-const"){
		std::array a = {0x0123, 0x4567, 0x89AB, 0xCDEF, 0x1032, 0x5476, 0x98BA, 0xDCFE};
		const RefArray b = {a[3], a[0], a[2], a[1], a[7], a[4], a[6], a[5]};
		static_assert (b.size() == 8);
		static_assert (std::is_same_v<decltype(b)::value_type, int>);
		CHECK(b.at(3) == 0x4567);
		CHECK(b.at(6) == 0x98BA);
		CHECK_THROWS(b.at(8));
		CHECK_THROWS(b.at(9));
		CHECK_THROWS(b.at(std::size_t(-1)));
		b.at(6) = 0x78429832;
		CHECK(a[6] == 0x78429832);
		CHECK(b[0] == 0xCDEF);
		CHECK(b[5] == 0x1032);
		CHECK(b[6] == 0x78429832);
		b[6] = 0x3344BBAA;
		CHECK(a[6] == 0x3344BBAA);
		CHECK(b.front() == 0xCDEF);
		CHECK(b.back() == 0x5476);
		CHECK(reinterpret_cast<const void*>(b.data()) == reinterpret_cast<const void*>(&b));
		CHECK(std::size(b.data()) == b.size());
		CHECK(b.begin() == &b.data()[0]);
		CHECK(b.end() == &b.data()[b.size()]);
		CHECK(b.cbegin() == &b.data()[0]);
		CHECK(b.cend() == &b.data()[b.size()]);
		CHECK(b.rbegin().base() == &b.data()[b.size()]);
		CHECK(b.rend().base() == &b.data()[0]);
		CHECK(b.crbegin().base() == &b.data()[b.size()]);
		CHECK(b.crend().base() == &b.data()[0]);
		auto it = b.begin();
		CHECK(*it++ == 0xCDEF);
		CHECK(*it++ == 0x0123);
		CHECK(*it++ == 0x89AB);
		CHECK(*it++ == 0x4567);
		CHECK(*it++ == 0xDCFE);
		CHECK(*it++ == 0x1032);
		CHECK(*it++ == 0x3344BBAA);
		CHECK(*it++ == 0x5476);
		CHECK(it == b.end());
		auto rit = b.rbegin();
		CHECK(*rit++ == 0x5476);
		CHECK(*rit++ == 0x3344BBAA);
		CHECK(*rit++ == 0x1032);
		CHECK(*rit++ == 0xDCFE);
		CHECK(*rit++ == 0x4567);
		CHECK(*rit++ == 0x89AB);
		CHECK(*rit++ == 0x0123);
		CHECK(*rit++ == 0xCDEF);
		CHECK(rit == b.rend());
		CHECK_FALSE(b.empty());
		auto c = a;
		b.fill(0x5A55A5A5);
		for(auto& i : a)
			CHECK(i == 0x5A55A5A5);
		auto d = pw_pbl::make_RefArray(c[3], c[0], c[2], c[1], c[7], c[4], c[6], c[5]);
		b = d;
		CHECK(c == a);
		const auto e = a;
		b.fill(0x22446688);
		b.swap(d);
		for(auto& i : c)
			CHECK(i == 0x22446688);
		CHECK(e == a);
		b.fill(0x33557799);
		for(auto& i : a)
			CHECK(i == 0x33557799);
		const auto f = pw_pbl::make_RefArray(e[3], e[0], e[2], e[1], e[7], e[4], e[6], e[5]);
		b = f;
		CHECK(e == a);
		CHECK(b == f);
		CHECK_FALSE(b != f);
		a[5] = 0x88423;
		CHECK_FALSE(b == f);
		CHECK(b != f);
		auto& g = b.slice<2, 5>();
		CHECK(g.size() == 4);
		CHECK(g.begin() == b.begin() + 2);
		CHECK(g.end() == b.begin() + 6);
	}
	SUBCASE("non-const const"){
		const std::array a = {0x0123, 0x4567, 0x89AB, 0xCDEF, 0x1032, 0x5476, 0x98BA, 0xDCFE};
		RefArray b = {a[3], a[0], a[2], a[1], a[7], a[4], a[6], a[5]};
		static_assert (b.size() == 8);
		static_assert (std::is_same_v<decltype(b)::value_type, const int>);
		CHECK(b.at(3) == 0x4567);
		CHECK(b.at(6) == 0x98BA);
		CHECK_THROWS(b.at(8));
		CHECK_THROWS(b.at(9));
		CHECK_THROWS(b.at(std::size_t(-1)));
		CHECK(b[0] == 0xCDEF);
		CHECK(b[5] == 0x1032);
		CHECK(b[6] == 0x98BA);
		CHECK(b.front() == 0xCDEF);
		CHECK(b.back() == 0x5476);
		CHECK(reinterpret_cast<void*>(b.data()) == reinterpret_cast<void*>(&b));
		CHECK(std::size(b.data()) == b.size());
		CHECK(b.begin() == &b.data()[0]);
		CHECK(b.end() == &b.data()[b.size()]);
		CHECK(b.cbegin() == &b.data()[0]);
		CHECK(b.cend() == &b.data()[b.size()]);
		CHECK(b.rbegin().base() == &b.data()[b.size()]);
		CHECK(b.rend().base() == &b.data()[0]);
		CHECK(b.crbegin().base() == &b.data()[b.size()]);
		CHECK(b.crend().base() == &b.data()[0]);
		auto it = b.begin();
		CHECK(*it++ == 0xCDEF);
		CHECK(*it++ == 0x0123);
		CHECK(*it++ == 0x89AB);
		CHECK(*it++ == 0x4567);
		CHECK(*it++ == 0xDCFE);
		CHECK(*it++ == 0x1032);
		CHECK(*it++ == 0x98BA);
		CHECK(*it++ == 0x5476);
		CHECK(it == b.end());
		auto rit = b.rbegin();
		CHECK(*rit++ == 0x5476);
		CHECK(*rit++ == 0x98BA);
		CHECK(*rit++ == 0x1032);
		CHECK(*rit++ == 0xDCFE);
		CHECK(*rit++ == 0x4567);
		CHECK(*rit++ == 0x89AB);
		CHECK(*rit++ == 0x0123);
		CHECK(*rit++ == 0xCDEF);
		CHECK(rit == b.rend());
		CHECK_FALSE(b.empty());
		auto c = a;
		auto d = pw_pbl::make_RefArray(c[3], c[0], c[2], c[1], c[7], c[4], c[6], c[5]);
		CHECK(b == d);
		CHECK_FALSE(b != d);
		c[3] = 0x88235;
		CHECK_FALSE(b == d);
		CHECK(b != d);
		const auto e = a;
		const auto f = pw_pbl::make_RefArray(e[3], e[0], e[2], e[1], e[7], e[4], e[6], e[5]);
		CHECK(b == f);
		CHECK_FALSE(b != f);
		auto& g = b.slice<2, 5>();
		CHECK(g.size() == 4);
		CHECK(g.begin() == b.begin() + 2);
		CHECK(g.end() == b.begin() + 6);
	}
	SUBCASE("const const"){
		const std::array a = {0x0123, 0x4567, 0x89AB, 0xCDEF, 0x1032, 0x5476, 0x98BA, 0xDCFE};
		const RefArray b = {a[3], a[0], a[2], a[1], a[7], a[4], a[6], a[5]};
		static_assert (b.size() == 8);
		static_assert (std::is_same_v<decltype(b)::value_type, const int>);
		CHECK(b.at(3) == 0x4567);
		CHECK(b.at(6) == 0x98BA);
		CHECK_THROWS(b.at(8));
		CHECK_THROWS(b.at(9));
		CHECK_THROWS(b.at(std::size_t(-1)));
		CHECK(b[0] == 0xCDEF);
		CHECK(b[5] == 0x1032);
		CHECK(b[6] == 0x98BA);
		CHECK(b.front() == 0xCDEF);
		CHECK(b.back() == 0x5476);
		CHECK(reinterpret_cast<const void*>(b.data()) == reinterpret_cast<const void*>(&b));
		CHECK(std::size(b.data()) == b.size());
		CHECK(b.begin() == &b.data()[0]);
		CHECK(b.end() == &b.data()[b.size()]);
		CHECK(b.cbegin() == &b.data()[0]);
		CHECK(b.cend() == &b.data()[b.size()]);
		CHECK(b.rbegin().base() == &b.data()[b.size()]);
		CHECK(b.rend().base() == &b.data()[0]);
		CHECK(b.crbegin().base() == &b.data()[b.size()]);
		CHECK(b.crend().base() == &b.data()[0]);
		auto it = b.begin();
		CHECK(*it++ == 0xCDEF);
		CHECK(*it++ == 0x0123);
		CHECK(*it++ == 0x89AB);
		CHECK(*it++ == 0x4567);
		CHECK(*it++ == 0xDCFE);
		CHECK(*it++ == 0x1032);
		CHECK(*it++ == 0x98BA);
		CHECK(*it++ == 0x5476);
		CHECK(it == b.end());
		auto rit = b.rbegin();
		CHECK(*rit++ == 0x5476);
		CHECK(*rit++ == 0x98BA);
		CHECK(*rit++ == 0x1032);
		CHECK(*rit++ == 0xDCFE);
		CHECK(*rit++ == 0x4567);
		CHECK(*rit++ == 0x89AB);
		CHECK(*rit++ == 0x0123);
		CHECK(*rit++ == 0xCDEF);
		CHECK(rit == b.rend());
		CHECK_FALSE(b.empty());
		auto c = a;
		auto d = pw_pbl::make_RefArray(c[3], c[0], c[2], c[1], c[7], c[4], c[6], c[5]);
		CHECK(b == d);
		CHECK_FALSE(b != d);
		c[3] = 0x88235;
		CHECK_FALSE(b == d);
		CHECK(b != d);
		const auto e = a;
		const auto f = pw_pbl::make_RefArray(e[3], e[0], e[2], e[1], e[7], e[4], e[6], e[5]);
		CHECK(b == f);
		CHECK_FALSE(b != f);
		auto& g = b.slice<2, 5>();
		CHECK(g.size() == 4);
		CHECK(g.begin() == b.begin() + 2);
		CHECK(g.end() == b.begin() + 6);
	}
}
